{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Asking Humans for Help: Customizing State in LangGraph\n",
    "\n",
    "- Author: [Hwayoung Cha](https://github.com/forwardyoung)\n",
    "- Peer Review: []()\n",
    "- Proofread : [Chaeyoon Kim](https://github.com/chaeyoonyunakim)\n",
    "- This is a part of [LangChain Open Tutorial](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial)\n",
    "\n",
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/17-LangGraph/01-Core-Features/08-LangGraph-State-Customization.ipynb)[![Open in GitHub](https://img.shields.io/badge/Open%20in%20GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/17-LangGraph/01-Core-Features/08-LangGraph-State-Customization.ipynb)\n",
    "## Overview\n",
    "\n",
    "This tutorial demonstrates how to extend a chatbot using LangGraph by adding a **\"human\" node**, allowing the system to optionally ask humans for help. It introduces state customization with an \"ask_human\" flag and shows how to handle interruptions and manual state updates. The tutorial also covers graph visualization, conditional logic, and integrating tools like web search and human assistance.\n",
    "\n",
    "### Table of Contents\n",
    "\n",
    "- [Overview](#overview)\n",
    "- [Environement Setup](#environment-setup)\n",
    "- [Setting Up the Node to Ask Humans for Help](#setting-up-the-node-to-ask-humans-for-help)\n",
    "- [Setting Up the Human Node](#setting-up-the-human-node)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## References\n",
    "\n",
    "- [Tavily Search](https://python.langchain.com/docs/integrations/tools/tavily_search/)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Environment Setup\n",
    "\n",
    "Set up the environment. You may refer to [Environment Setup](https://wikidocs.net/257836) for more details.\n",
    "\n",
    "**[Note]**\n",
    "- ```langchain-opentutorial``` is a package that provides a set of easy-to-use environment setup, useful functions and utilities for tutorials. \n",
    "- You can checkout the [```langchain-opentutorial```](https://github.com/LangChain-OpenTutorial/langchain-opentutorial-pypi) for more details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n",
      "[notice] A new release of pip is available: 23.1 -> 24.3.1\n",
      "[notice] To update, run: python.exe -m pip install --upgrade pip\n"
     ]
    }
   ],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install langchain-opentutorial"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install required packages\n",
    "from langchain_opentutorial import package\n",
    "\n",
    "package.install(\n",
    "    [\n",
    "        \"langsmith\",\n",
    "        \"langgraph\",\n",
    "        \"langchain_core\",\n",
    "        \"langchain_openai\",\n",
    "        \"langchain_community\"\n",
    "    ],\n",
    "    verbose=False,\n",
    "    upgrade=False,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Environment variables have been set successfully.\n"
     ]
    }
   ],
   "source": [
    "# Set environment variables\n",
    "from langchain_opentutorial import set_env\n",
    "\n",
    "set_env(\n",
    "    {\n",
    "        \"OPENAI_API_KEY\": \"\",\n",
    "        \"LANGSMITH_TRACING_V2\": \"true\",\n",
    "        \"LANGSMITH_ENDPOINT\": \"https://api.smith.langchain.com\",\n",
    "        \"LANGCHAIN_API_KEY\": \"\",\n",
    "        \"LANGCHAIN_PROJECT\": \"\", # set the project name same as the title\n",
    "        \"TAVILY_API_KEY\": \"\"\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can alternatively set OPENAI_API_KEY in .env file and load it.\n",
    "\n",
    "[Note] This is not necessary if you've already set OPENAI_API_KEY in previous steps."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Configuration file to manage API keys as environment variables\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# Load API key information\n",
    "load_dotenv(override=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setting Up the Node to Ask Humans for Help\n",
    "\n",
    "Extends the ```State``` class to include an ```ask_human``` flag and defines the ```HumanRequest``` tool schema using ```TypedDict``` and ```BaseModel```. This allows the chatbot to formally request human assistance when needed, adding flexibility to its decision-making process."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated\n",
    "from typing_extensions import TypedDict\n",
    "from langchain_community.tools import TavilySearchResults\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langgraph.graph import StateGraph, START\n",
    "from langgraph.graph.message import add_messages\n",
    "from langgraph.prebuilt import ToolNode, tools_condition"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This time, we will add a state (```ask_human```) to determine whether the chatbot should ask a human for help during the conversation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "class State(TypedDict):\n",
    "    # List for messages\n",
    "    messages: Annotated[list, add_messages]\n",
    "    # State to determine whether to ask a human for help\n",
    "    ask_human: bool"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We define the schema for the ```human``` request."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pydantic import BaseModel\n",
    "\n",
    "\n",
    "class HumanRequest(BaseModel):\n",
    "    \"\"\"Forward the conversation to an expert. Use when you can't assist directly or the user needs assistance that exceeds your authority.\n",
    "    To use this function, pass the user's 'request' so that an expert can provide appropriate guidance.\n",
    "    \"\"\"\n",
    "\n",
    "    request: str"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, we define the chatbot node. \n",
    "The key modification here is that the chatbot will toggle the ```ask_human``` flag if it calls the ```RequestAssistance``` flag."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "# Add tools\n",
    "tool = TavilySearchResults(max_results=3)\n",
    "\n",
    "# Add the list of tools (including the HumanRequest tool)\n",
    "tools = [tool, HumanRequest]\n",
    "\n",
    "# Add the LLM\n",
    "llm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0)\n",
    "\n",
    "# Bind tools to the LLM\n",
    "llm_with_tools = llm.bind_tools(tools)\n",
    "\n",
    "def chatbot(state: State):\n",
    "    # Generate a response using the LLM tool calls\n",
    "    response = llm_with_tools.invoke(state[\"messages\"])\n",
    "\n",
    "    # Initialize the ask_human flag\n",
    "    ask_human = False\n",
    "\n",
    "    # If there is a tool call and its name is 'HumanRequest'\n",
    "    if response.tool_calls and response.tool_calls[0][\"name\"] == HumanRequest.__name__:\n",
    "        ask_human = True\n",
    "\n",
    "    # Return the messages and the ask_human state\n",
    "    return {\"messages\": [response], \"ask_human\": ask_human}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, we create the graph builder and add the ```chatbot``` and ```tools``` nodes to the graph, as before."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<langgraph.graph.state.StateGraph at 0x1a428e8dfd0>"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Initialize the state graph\n",
    "graph_builder = StateGraph(State)\n",
    "\n",
    "# Add the chatbot node\n",
    "graph_builder.add_node(\"chatbot\", chatbot)\n",
    "\n",
    "# Add the tools node\n",
    "graph_builder.add_node(\"tools\", ToolNode(tools=[tool]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setting Up the Human Node\n",
    "\n",
    "Next, we create the ```human``` node. \n",
    "\n",
    "This node primarily serves as a placeholder to trigger an interrupt in the graph. If the user does not manually update the state during the ```interrupt```, the LLM will insert a tool message to indicate that the human was asked for help but did not respond.\n",
    "\n",
    "This node also resets the ```ask_human``` flag to ensure the graph does not revisit the node unless another request is made."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> Reference Image\n",
    "\n",
    "![](./assets/08-langgraph-state-customization.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.messages import AIMessage, ToolMessage\n",
    "\n",
    "# Function to create a response message (for generating ToolMessage)\n",
    "def create_response(response: str, ai_message: AIMessage):\n",
    "    return ToolMessage(\n",
    "        content=response,\n",
    "        tool_call_id=ai_message.tool_calls[0][\"id\"],\n",
    "    )\n",
    "\n",
    "# Human node processing\n",
    "def human_node(state: State):\n",
    "    new_messages = []\n",
    "    if not isinstance(state[\"messages\"][-1], ToolMessage):\n",
    "        # If there is no response from the human\n",
    "        new_messages.append(\n",
    "            create_response(\"No response from human.\", state[\"messages\"][-1])\n",
    "        )\n",
    "    return {\n",
    "        # Add new messages\n",
    "        \"messages\": new_messages,\n",
    "        # Reset the flag\n",
    "        \"ask_human\": False,\n",
    "    }\n",
    "\n",
    "# Add the human node to the graph\n",
    "if \"human\" not in graph_builder.nodes: \n",
    "    graph_builder.add_node(\"human\", human_node)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, we define the conditional logic.\n",
    "\n",
    "The ```select_next_node``` function routes the path to the ```human``` node if the flag is set. Otherwise, it uses the prebuilt ```tools_condition``` function to select the next node.\n",
    "\n",
    "The ```tools_condition``` function simply checks if the ```chatbot``` used ```tool_calls``` in the response message.\n",
    "\n",
    "If so, it routes to the ```action``` node. Otherwise, it ends the graph."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<langgraph.graph.state.StateGraph at 0x1a428e8dfd0>"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langgraph.graph import END\n",
    "\n",
    "# Function to select the next node\n",
    "def select_next_node(state: State):\n",
    "    # Check if the chatbot should ask a human\n",
    "    if state[\"ask_human\"]:\n",
    "        return \"human\"\n",
    "    # Otherwise, follow the same path as before\n",
    "    return tools_condition(state)\n",
    "\n",
    "# Add conditional edges\n",
    "graph_builder.add_conditional_edges(\n",
    "    \"chatbot\",\n",
    "    select_next_node,\n",
    "    {\"human\": \"human\", \"tools\": \"tools\", END: END},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, we connect the edges and compile the graph."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Add edge: from 'tools' to 'chatbot'\n",
    "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
    "\n",
    "# Add edge: from 'human' to 'chatbot'\n",
    "graph_builder.add_edge(\"human\", \"chatbot\")\n",
    "\n",
    "# Add edge: from START to 'chatbot'\n",
    "graph_builder.add_edge(START, \"chatbot\")\n",
    "\n",
    "# Initialize memory storage\n",
    "memory = MemorySaver()\n",
    "\n",
    "# Compile the graph: use memory checkpointing\n",
    "graph = graph_builder.compile(\n",
    "    checkpointer=memory,\n",
    "    # Set interrupt before 'human'\n",
    "    interrupt_before=[\"human\"],\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's visualize the graph."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Image, display\n",
    "from langchain_core.runnables.graph import MermaidDrawMethod\n",
    "\n",
    "# Visualize the graph\n",
    "display(\n",
    "    Image(\n",
    "        graph.get_graph().draw_mermaid_png(\n",
    "            draw_method=MermaidDrawMethod.API,\n",
    "        )\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ```chatbot``` node behaves as follows:\n",
    "\n",
    "- The chatbot can ask a human for help (chatbot->select->human)\n",
    "- It can call a search engine tool (chatbot->select->action)\n",
    "- Or it can respond directly (chatbot->select-> **end** ).\n",
    "\n",
    "Once an action or request is made, the graph switches back to the ```chatbot``` node to continue the task."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "I need expert help to build this AI agent. Can you request assistance?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  HumanRequest (call_wx9Kdr8GFUqbPLz0WYIDMDfn)\n",
      " Call ID: call_wx9Kdr8GFUqbPLz0WYIDMDfn\n",
      "  Args:\n",
      "    request: I need assistance in building an AI agent. I'm looking for guidance on the design, implementation, and best practices for developing an effective AI agent.\n"
     ]
    }
   ],
   "source": [
    "# user_input = \"I need expert help to build this AI agent. Please search for an answer.\" (Case where it performs a web search instead of asking a human)\n",
    "user_input = \"I need expert help to build this AI agent. Can you request assistance?\"\n",
    "\n",
    "# Config setup\n",
    "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
    "\n",
    "# Stream or call the second positional argument as configuration\n",
    "events = graph.stream(\n",
    "    {\"messages\": [(\"user\", user_input)]}, config, stream_mode=\"values\"\n",
    ")\n",
    "for event in events:\n",
    "    if \"messages\" in event:\n",
    "        # Pretty print the last message\n",
    "        event[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Notice:** The ```LLM``` has called the provided \"```HumanRequest```\" tool, and an interrupt has been set. Let's check the graph state."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('human',)"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Create a snapshot of the graph state\n",
    "snapshot = graph.get_state(config)\n",
    "\n",
    "# Access the next snapshot state\n",
    "snapshot.next"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The graph state is actually **interrupted** before the ```'human'``` node. In this scenario, you can act as the \"expert\" and manually update the state by adding a new ```ToolMessage``` with your input.\n",
    "\n",
    "To respond to the chatbot's request, follow these steps:\n",
    "\n",
    "1. Create a ```ToolMessage``` containing your response. This will be passed back to the ```chatbot```.\n",
    "2. Call ```update_state``` to manually update the graph state."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'configurable': {'thread_id': '1',\n",
       "  'checkpoint_ns': '',\n",
       "  'checkpoint_id': '1efd995f-bb3e-6210-8002-424fec5dff73'}}"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Extract the AI message\n",
    "ai_message = snapshot.values[\"messages\"][-1]\n",
    "\n",
    "# Create a human response\n",
    "human_response = (\n",
    "    \"Experts are here to help! We highly recommend checking out LangGraph for building your agent. \"\n",
    "    \"It is much more stable and scalable than a simple autonomous agent. \"\n",
    "    \"You can find more information at https://wikidocs.net/233785.\"\n",
    ")\n",
    "\n",
    "# Create a tool message\n",
    "tool_message = create_response(human_response, ai_message)\n",
    "\n",
    "# Update the graph state\n",
    "graph.update_state(config, {\"messages\": [tool_message]})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can check the state to confirm that the response has been added."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[HumanMessage(content='I need expert help to build this AI agent. Can you request assistance?', additional_kwargs={}, response_metadata={}, id='68c43e31-4350-4062-8d81-b7547f24c727'),\n",
       " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_wx9Kdr8GFUqbPLz0WYIDMDfn', 'function': {'arguments': '{\"request\":\"I need assistance in building an AI agent. I\\'m looking for guidance on the design, implementation, and best practices for developing an effective AI agent.\"}', 'name': 'HumanRequest'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 44, 'prompt_tokens': 151, 'total_tokens': 195, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_72ed7ab54c', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-12747fa0-8dfc-4450-9867-d5537ab3d74b-0', tool_calls=[{'name': 'HumanRequest', 'args': {'request': \"I need assistance in building an AI agent. I'm looking for guidance on the design, implementation, and best practices for developing an effective AI agent.\"}, 'id': 'call_wx9Kdr8GFUqbPLz0WYIDMDfn', 'type': 'tool_call'}], usage_metadata={'input_tokens': 151, 'output_tokens': 44, 'total_tokens': 195, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}),\n",
       " ToolMessage(content='Experts are here to help! We highly recommend checking out LangGraph for building your agent. It is much more stable and scalable than a simple autonomous agent. You can find more information at https://wikidocs.net/233785.', id='d3907e79-eaca-4e4b-af0c-730af576f292', tool_call_id='call_wx9Kdr8GFUqbPLz0WYIDMDfn')]"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Get the message values from the graph state\n",
    "graph.get_state(config).values[\"messages\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, we **resume** the graph by passing ```None``` as the input."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "\n",
      "Experts are here to help! We highly recommend checking out LangGraph for building your agent. It is much more stable and scalable than a simple autonomous agent. You can find more information at https://wikidocs.net/233785.\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "\n",
      "Experts are here to help! We highly recommend checking out LangGraph for building your agent. It is much more stable and scalable than a simple autonomous agent. You can find more information at https://wikidocs.net/233785.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "I've requested assistance, and the experts recommend checking out LangGraph for building your AI agent. It's noted to be more stable and scalable than a simple autonomous agent. You can find more information [here](https://wikidocs.net/233785).\n"
     ]
    }
   ],
   "source": [
    "# Generate an event stream from the graph\n",
    "events = graph.stream(None, config, stream_mode=\"values\")\n",
    "\n",
    "# Process each event\n",
    "for event in events:\n",
    "    # Print the last message if messages are present\n",
    "    if \"messages\" in event:\n",
    "        event[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, let's check the final result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "I need expert help to build this AI agent. Can you request assistance?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  HumanRequest (call_wx9Kdr8GFUqbPLz0WYIDMDfn)\n",
      " Call ID: call_wx9Kdr8GFUqbPLz0WYIDMDfn\n",
      "  Args:\n",
      "    request: I need assistance in building an AI agent. I'm looking for guidance on the design, implementation, and best practices for developing an effective AI agent.\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "\n",
      "Experts are here to help! We highly recommend checking out LangGraph for building your agent. It is much more stable and scalable than a simple autonomous agent. You can find more information at https://wikidocs.net/233785.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "I've requested assistance, and the experts recommend checking out LangGraph for building your AI agent. It's noted to be more stable and scalable than a simple autonomous agent. You can find more information [here](https://wikidocs.net/233785).\n"
     ]
    }
   ],
   "source": [
    "# Check the final state\n",
    "state = graph.get_state(config)\n",
    "\n",
    "# Print the messages step by step\n",
    "for message in state.values[\"messages\"]:\n",
    "    message.pretty_print()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-opentutorial-GHgbjDj7-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
